package src;

import java.io.IOException;

import lib.ArgumentValidator;
import lib.Command;
import lib.messaging.NetworkAddress;
import lib.paxos.AcceptMessage;
import lib.paxos.AckMessage;
import lib.paxos.DecideMessage;
import lib.paxos.IPaxosFunctionality;
import lib.paxos.PaxosException;
import lib.paxos.PrepareMessage;
import lib.paxos.SyncMessage;
import lib.rpc.IRPCMethods;
import lib.rpc.RPCInvokePacket;
import lib.rpc.RPCResultCode;
import lib.rpc.RPCResultPacket;

/**
 * Functionality for handling invoke requests on server side
 * @implements {@link IServerRPCHandler}
 * @author abhip
 *
 */
public class ServerRPCHandler implements IServerRPCHandler
{
    private IPaxosFunctionality paxosFunctionality;
    private NetworkAddress serverAddress;
    private IRPCMethods serverRPCMethods;


    public ServerRPCHandler(
            NetworkAddress serverAddress,
            IRPCMethods serverRPCMethods,
            IPaxosFunctionality paxosFunctionality)
    {
        ArgumentValidator.IsNotNull("serverAddress", serverAddress);
        ArgumentValidator.IsNotNull("serverRPCMethods", serverRPCMethods);
        ArgumentValidator.IsNotNull("paxosFunctionality", paxosFunctionality);
        
        this.serverAddress = serverAddress;
        this.serverRPCMethods = serverRPCMethods;
        this.paxosFunctionality = paxosFunctionality;
    }

    private RPCResultPacket HandleAcceptRPC(RPCInvokePacket rpcInvokePacket)
    {
        try
        {
            AcceptMessage acceptMsg = (AcceptMessage) rpcInvokePacket.Params()[0];
            AckMessage ackMsg = serverRPCMethods.Accept(
                    rpcInvokePacket.ClientAddress(),
                    rpcInvokePacket.ServerAddress(),
                    acceptMsg);

            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.SUCCESS,
                    ackMsg);
        }
        catch (ClassNotFoundException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
        catch (IOException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
        catch (PaxosException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
    }

    private RPCResultPacket HandleAppendLineToFileRPC(RPCInvokePacket rpcInvokePacket)
    {
        if (rpcInvokePacket.Params().length < 3)
        {
            String errorMsg = String.format(
                    "%s rpcInvokePacket is missing roundID, fileName or data. rpcInvokePacket details: %s",
                    IRPCMethods.AppendLineToFileMethodName,
                    rpcInvokePacket.toString());
            
            // Return errorMsg as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    errorMsg);
        }
        
        int roundID = (Integer) rpcInvokePacket.Params()[0];
        Command command = new Command(IRPCMethods.AppendLineToFileMethodName, rpcInvokePacket.Params());
        
        if (paxosFunctionality.RunPaxos(roundID, command))
        {
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.SUCCESS,
                    paxosFunctionality.GetResult());
        }
        else
        {
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    paxosFunctionality.GetResult());
        }
    }

    private RPCResultPacket HandleCreateFileRPC(RPCInvokePacket rpcInvokePacket) 
    {
        if (rpcInvokePacket.Params().length < 2) {
            String errorMsg = String.format(
                    "%s rpcInvokePacket is missing roundID or fileName. rpcInvokePacket details: %s",
                    IRPCMethods.CreateFileMethodName,
                    rpcInvokePacket.toString());

            // Return errorMsg as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    errorMsg);
        }
        
        int roundID = (Integer) rpcInvokePacket.Params()[0];
        Command command = new Command(IRPCMethods.CreateFileMethodName, rpcInvokePacket.Params());
        
        if (paxosFunctionality.RunPaxos(roundID, command))
        {
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.SUCCESS,
                    paxosFunctionality.GetResult());
        }
        else
        {
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    paxosFunctionality.GetResult());
        }
    }

    private RPCResultPacket HandleDecideRPC(RPCInvokePacket rpcInvokePacket)
    {
        try
        {
        	DecideMessage decidetMsg = (DecideMessage) rpcInvokePacket.Params()[0];
            AckMessage ackMsg = serverRPCMethods.Decide(
                    rpcInvokePacket.ClientAddress(),
                    rpcInvokePacket.ServerAddress(),
                    decidetMsg);

            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.SUCCESS,
                    ackMsg);
        }
        catch (ClassNotFoundException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
        catch (IOException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
        catch (PaxosException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
    }

    private RPCResultPacket HandleGetLineFromFileRPC(RPCInvokePacket rpcInvokePacket) 
    {
        if (rpcInvokePacket.Params().length < 3) 
        {
            String errorMsg = String.format(
                    "%s rpcInvokePacket is missing roundID or fileName or lineNumber. rpcInvokePacket details: %s",
                    IRPCMethods.GetLineFromFileMethodName,
                    rpcInvokePacket.toString());

            // Return errorMsg as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED, errorMsg);
        }

        int roundID = (Integer) rpcInvokePacket.Params()[0];
        Command command = new Command(IRPCMethods.GetLineFromFileMethodName, rpcInvokePacket.Params());
        
        if (paxosFunctionality.RunPaxos(roundID, command))
        {
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.SUCCESS,
                    paxosFunctionality.GetResult());
        }
        else
        {
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    paxosFunctionality.GetResult());
        }
    }
    
    private RPCResultPacket HandleJoinRPC(RPCInvokePacket rpcInvokePacket)
    {
        if (rpcInvokePacket.Params().length < 2)
        {
            String errorMsg = String.format(
                    "%s rpcInvokePacket is missing roundID or newServerAddress. rpcInvokePacket details: %s",
                    IRPCMethods.JoinMethodName,
                    rpcInvokePacket.toString());
            
            // Return errorMsg as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    errorMsg);
        }
        
        int roundID = (Integer) rpcInvokePacket.Params()[0];
        Command command = new Command(IRPCMethods.JoinMethodName, rpcInvokePacket.Params());
        
        if (paxosFunctionality.RunPaxos(roundID, command))
        {
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.SUCCESS,
                    paxosFunctionality.GetResult());
        }
        else
        {
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    paxosFunctionality.GetResult());
        }
    }
    
    private RPCResultPacket HandlePrepareRPC(RPCInvokePacket rpcInvokePacket)
    {
        try
        {
            PrepareMessage prepareMsg = (PrepareMessage) rpcInvokePacket.Params()[0];
            AckMessage ackMsg = serverRPCMethods.Prepare(
                    rpcInvokePacket.ClientAddress(),
                    rpcInvokePacket.ServerAddress(),
                    prepareMsg);

            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.SUCCESS,
                    ackMsg);
        }
        catch (ClassNotFoundException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
        catch (IOException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
        catch (PaxosException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
    }
    
    @Override
    public RPCResultPacket HandleRPC(RPCInvokePacket rpcInvokePacket)
            throws UnsupportedOperationException 
    {    
        if (rpcInvokePacket.MethodName().equalsIgnoreCase(IRPCMethods.CreateFileMethodName)) 
        {
            return HandleCreateFileRPC(rpcInvokePacket);
        }

        if (rpcInvokePacket.MethodName().equalsIgnoreCase(IRPCMethods.AppendLineToFileMethodName)) 
        {
            return HandleAppendLineToFileRPC(rpcInvokePacket);
        }

        if (rpcInvokePacket.MethodName().equalsIgnoreCase(IRPCMethods.GetLineFromFileMethodName)) 
        {
            return HandleGetLineFromFileRPC(rpcInvokePacket);
        }

        if (rpcInvokePacket.MethodName().equalsIgnoreCase(IRPCMethods.PrepareMethodName)) 
        {
            return HandlePrepareRPC(rpcInvokePacket);
        }
        
        if (rpcInvokePacket.MethodName().equalsIgnoreCase(IRPCMethods.AcceptMethodName)) 
        {
            return HandleAcceptRPC(rpcInvokePacket);
        }
        
        if (rpcInvokePacket.MethodName().equalsIgnoreCase(IRPCMethods.DecideMethodName)) 
        {
            return HandleDecideRPC(rpcInvokePacket);
        }
        
        if (rpcInvokePacket.MethodName().equalsIgnoreCase(IRPCMethods.SyncMethodName)) 
        {
            return HandleSyncRPC(rpcInvokePacket);
        }
        
        if (rpcInvokePacket.MethodName().equalsIgnoreCase(IRPCMethods.JoinMethodName)) 
        {
            return HandleJoinRPC(rpcInvokePacket);
        }

        else {
            throw new UnsupportedOperationException(
                    String.format(
                            "Unsupported rpcInvokePacket: '%s'",
                            rpcInvokePacket.toString()));
        }
    }
    
    private RPCResultPacket HandleSyncRPC(RPCInvokePacket rpcInvokePacket)
    {
        try
        {
            SyncMessage syncMsg = (SyncMessage) rpcInvokePacket.Params()[0];
            AckMessage ackMsg = serverRPCMethods.Sync(
                    rpcInvokePacket.ClientAddress(),
                    rpcInvokePacket.ServerAddress(),
                    syncMsg);

            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.SUCCESS,
                    ackMsg);
        }
        catch (ClassNotFoundException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
        catch (IOException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
        catch (PaxosException e)
        {
            // Return the exception as result
            return new RPCResultPacket(
                    rpcInvokePacket.RequestID(),
                    serverAddress,
                    RPCResultCode.FAILED,
                    e);
        }
    }
}
